/*
* Copyright (c) 2011 adVolition
*
* Permission is hereby granted, free of charge, to any person
* obtaining a clone of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* clone, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* Except as contained in this notice, the name(s) of the above
* copyright holders shall not be used in advertising or otherwise to
* promote the sale, use or other dealings in this Software without prior
* written authorization.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
 */

package ArenaSimulator;
import java.awt.Graphics2D;
import com.thoughtworks.xstream.annotations.*;
import java.util.ArrayList;

/**
 * The Challenge class describes the environment in which the robot will operate.
 */
@XStreamAlias("challenge") public class Challenge {

    private transient Double _xDimension;
    private transient Double _yDimension;
    private transient Double _xOffset;
    private transient Double _yOffset;
    private transient int _currentObjective; // The next feature that the robot must reach.
    private transient Double _score;

    @XStreamAlias("features") @XStreamImplicit private ArrayList<Feature> _features;

    public Challenge() {
        configureObject();
    }

    public Object readResolve() {
        configureObject();
        return this;
    }

    /**
     * Configures the transient parameters for a newly-created object.
     */
    private void configureObject() {
        if (_features == null) {
            _features = new ArrayList<Feature>();
            Double random = Math.random()*50.0 + 75.0;

            // Add barriers and robots to the arena.
            addLinearWall(5.0, 10.0, random, 10.0);
            addRectangularLedge(5.0, 5.0, random, random);          
        }

        _xOffset = getMinimumX();
        _yOffset = getMinimumY();

        _xDimension = getMaximumX() - _xOffset;
        _yDimension = getMaximumY() - _yOffset;
        reset();   
    }

    public void addLinearWall(Double aX, Double aY, Double bX, Double bY) {
        _features.add(new Feature("wall", 100.0));
        _features.get(_features.size() - 1).addNode(aX, aY);
        _features.get(_features.size() - 1).addNode(bX, bY);
        
    }

    public void addLinearLedge(Double aX, Double aY, Double bX, Double bY) {
        _features.add(new Feature("ledge", 100.0));
        _features.get(_features.size() - 1).addNode(aX, aY);
        _features.get(_features.size() - 1).addNode(bX, bY);
    }

    public void addRectangularWall(Double aX, Double aY, Double bX, Double bY) {
        _features.add(new Feature("wall", 100.0));
        _features.get(_features.size() - 1).addNode(aX, aY);
        _features.get(_features.size() - 1).addNode(bX, aY);
        _features.get(_features.size() - 1).addNode(bX, bY);
        _features.get(_features.size() - 1).addNode(aX, bY);
        _features.get(_features.size() - 1).addNode(aX, aY);
    }

    public void addRectangularLedge(Double aX, Double aY, Double bX, Double bY) {
        _features.add(new Feature("ledge", 100.0));
        _features.get(_features.size() - 1).addNode(aX, aY);
        _features.get(_features.size() - 1).addNode(bX, aY);
        _features.get(_features.size() - 1).addNode(bX, bY);
        _features.get(_features.size() - 1).addNode(aX, bY);
        _features.get(_features.size() - 1).addNode(aX, aY);
    }


    /**
     * Plots the arena at x, y using the specified scale.
     * @param x x coordinate for the upper left hand corner of the plot.
     * @param y y coordinate for the upper left hand corner of the plot.
     * @param scale pixels per centimeter.
     * @param g2d pointer to Graphics2D object.
     */
    public void draw(int x, int y, Double scale, Graphics2D g2d) {
        for (int i = 0; i < _features.size(); i ++) {
            _features.get(i).draw(x, y, scale, g2d);
        }
    }

    public Double getMaximumX() {
        Double maximum = 0.0;
        Double value = 0.0;
        for (int i = 0; i < _features.size(); i ++) {
            value = _features.get(i).getMaximumX();
            if (value > maximum) {
                maximum = value;
            }
        }
        return maximum;
    }

    public Double getMinimumX() {
        Double minimum = 0.0;
        Double value = 0.0;
        for (int i = 0; i < _features.size(); i ++) {
            value = _features.get(i).getMinimumX();
            if (value < minimum) {
                minimum = value;
            }
        }
        return minimum;
    }

    public Double getMaximumY() {
        Double maximum = 0.0;
        Double value = 0.0;
        for (int i = 0; i < _features.size(); i ++) {
            value = _features.get(i).getMaximumY();
            if (value > maximum) {
                maximum = value;
            }
        }
        return maximum;
    }

    public Double getMinimumY() {
        Double minimum = 0.0;
        Double value = 0.0;
        for (int i = 0; i < _features.size(); i ++) {
            value = _features.get(i).getMinimumY();
            if (value < minimum) {
                minimum = value;
            }
        }
        return minimum;
    }


    
    public Feature getFeature(int index) {
        return _features.get(index);
    }
    
    public int getFeatureCount() {
        return _features.size();
    }
    
    /**
     * Returns the total number of points that the robot has scored in the current challenge.
     * @return 
     */
    public Double getScore() {
        return _score;
    }
    
    /**
     * This function is called when the robot reaches its current objective.  As 
     * a result, the object is incremented to the next feature and the new feature's
     * timer is started.  If there are no more features, then the objective is not
     * incremented.
     */
    public void incrementObjective(Double currentTime) {
        if (_currentObjective < _features.size() - 1) {       
            _currentObjective += 1;
            _features.get(_currentObjective).startTimer(currentTime);
        }
        _score += _features.get(_currentObjective).stopTimer(currentTime);
    }
    
    /**
     * Accepts the challenge time and returns true if the challenge has ended.
     * @return 
     */
    public Boolean timeIsUp(Double challengeTime) {
        return getFeature(_currentObjective).timeIsUp(challengeTime);
    }
    
    /**
     * Resets the challenge by zeroing the current objective.
     */
    public void reset() {
        _score = 0.0;
        _currentObjective = 0;
        for (int i = 0; i < _features.size(); i ++) {
            _features.get(i).readResolve();
        }
    }

    /**
     * Checks to see if the robot encountered any challenge features.
     * @param robot Pointer to the robot object.
     * @param challengeTime Current time in seconds.
     */
    public void checkForCollisions(Robot robot, Double challengeTime) {
        for (int featureIndex = 0; featureIndex < _features.size(); featureIndex ++) {
            if (robot.encounteredFeature(_features.get(featureIndex))) {
                
                // Notify robot if it has hit a wall or fallen off a ledge.
                robot.setRobotIsStuck(_features.get(featureIndex).isWall());
                robot.setRobotHasFallen(_features.get(featureIndex).isLedge());
 
                // Determine whether robot had reached current challenge obective.
                if (featureIndex == _currentObjective) {
                    incrementObjective(challengeTime);                  
                }
                
                // Assumes the robot can only encounter one feature at a time.
                break;
            }
        }
    }
}
 